/*
 * $RCSfile: DataImport.java,v $$
 * $Revision: 1.1 $
 * $Date: 2013-4-24 $
 *
 * Copyright (C) 2008 Skine, Inc. All rights reserved.
 *
 * This software is the proprietary information of Skine, Inc.
 * Use is subject to license terms.
 */
package com.skin.webcat.exchange;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.skin.webcat.database.Column;
import com.skin.webcat.io.StringStream;
import com.skin.webcat.util.ClassUtil;

/**
 * <p>Title: DataImport</p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2006</p>
 * @author xuesong.net
 * @version 1.0
 */
public class DataImport {
    private static final int EOF = -1;
    private ProcessHandler processHandler;
    private static final Logger logger = LoggerFactory.getLogger(DataImport.class);

    /**
     * @param reader
     * @throws IOException
     * @throws SQLException
     */
    public void execute(Reader reader) throws IOException, SQLException {
        this.execute(reader, 500, false);
    }

    /**
     * @param reader
     * @param batchSize
     * @param clear
     * @throws IOException
     * @throws SQLException
     */
    public void execute(Reader reader, int batchSize, boolean clear) throws IOException, SQLException {
        String line = null;
        String tableName = null;
        List<String> list = new ArrayList<String>();
        StringStream stream = new StringStream();
        BufferedReader buffer = new BufferedReader(reader);

        // header
        while((line = buffer.readLine()) != null) {
            line = line.trim();

            if(line.length() < 1) {
                continue;
            }

            if(line.startsWith("#")) {
                continue;
            }

            tableName = line;
            break;
        }

        // header
        while((line = buffer.readLine()) != null) {
            line = line.trim();
            
            if(line.length() < 1) {
                continue;
            }

            if(line.startsWith("#")) {
                continue;
            }

            stream.setStream(line);
            this.parse(stream, list);
            break;
        }

        if(tableName == null) {
            throw new RuntimeException("Bad format: " + tableName);
        }

        int columnCount = list.size();

        if(columnCount < 1) {
            throw new RuntimeException("Bad format: " + tableName);
        }

        this.processHandler.setTableName(tableName);

        if(clear) {
            this.processHandler.delete("delete from " + tableName);
        }

        int count = 0;
        List<Column> columns = this.getColumns(list);
        Map<String, Object> map = new LinkedHashMap<String, Object>();
        DataSet dataSet = new DataSet(map);
        list.clear();

        while((line = buffer.readLine()) != null) {
            stream.setStream(line);
            this.parse(stream, list);

            if(list.size() != columnCount) {
                throw new RuntimeException("Bad format: " + tableName + "\r\n" + line);
            }

            map = getDataSet(columns, list, map);

            try {
                this.processHandler.addBatch(columns, dataSet);
                count++;

                if(count >= batchSize) {
                    if(logger.isDebugEnabled()) {
                        logger.debug("executeBatch.count: " + count);
                    }

                    count = 0;
                    this.processHandler.executeBatch();
                }
            }
            catch(SQLException e) {
                throw new SQLException("Error: " + tableName + "\r\n" + line, e);
            }

            list.clear();
            map.clear();
        }

        if(count > 0) {
            if(logger.isDebugEnabled()) {
                logger.debug("executeBatch.count: " + count);
            }
            count = 0;
            this.processHandler.executeBatch();
        }
    }

    /**
     * @param columns
     * @param data
     * @param map
     * @return Map<String, Object>
     */
    public Map<String, Object> getDataSet(List<Column> columns, List<String> data, Map<String, Object> map) {
        String columnName = null;
        String javaTypeName = null;
        Column column = null;

        for(int i = 0, size = columns.size(); i < size; i++) {
            column = columns.get(i);
            columnName = column.getColumnName();
            javaTypeName = column.getJavaTypeName();

            if(data.get(i) == null) {
                continue;
            }

            if(javaTypeName.equalsIgnoreCase("boolean") || javaTypeName.equalsIgnoreCase("Boolean")) {
                map.put(columnName, ClassUtil.cast(data.get(i), Boolean.class));
            }
            else if(javaTypeName.equalsIgnoreCase("byte") || javaTypeName.equalsIgnoreCase("Byte")) {
                map.put(columnName, ClassUtil.cast(data.get(i), Byte.class));
            }
            else if(javaTypeName.equalsIgnoreCase("short") || javaTypeName.equalsIgnoreCase("Short")) {
                map.put(columnName, ClassUtil.cast(data.get(i), Short.class));
            }
            else if(javaTypeName.equalsIgnoreCase("int") || javaTypeName.equalsIgnoreCase("Integer")) {
                map.put(columnName, ClassUtil.cast(data.get(i), Integer.class));
            }
            else if(javaTypeName.equalsIgnoreCase("float") || javaTypeName.equalsIgnoreCase("Float")) {
                map.put(columnName, ClassUtil.cast(data.get(i), Float.class));
            }
            else if(javaTypeName.equalsIgnoreCase("double") || javaTypeName.equalsIgnoreCase("Double")) {
                map.put(columnName, ClassUtil.cast(data.get(i), Double.class));
            }
            else if(javaTypeName.equalsIgnoreCase("long") || javaTypeName.equalsIgnoreCase("Long")) {
                map.put(columnName, ClassUtil.cast(data.get(i), Long.class));
            }
            else if(javaTypeName.equalsIgnoreCase("Date") || javaTypeName.equalsIgnoreCase("java.util.Date")) {
                map.put(columnName, this.getDate(data.get(i)));
            }
            else if(javaTypeName.equalsIgnoreCase("Timestamp") || javaTypeName.equalsIgnoreCase("java.sql.Timestamp")) {
                map.put(columnName, this.getTimestamp(data.get(i)));
            }
            else if(javaTypeName.equalsIgnoreCase("char") || javaTypeName.equalsIgnoreCase("String")) {
                map.put(columnName, data.get(i));
            }
            else {
                map.put(columnName, data.get(i));
            }
        }

        return map;
    }

    /**
     * @param list
     * @return List<Column>
     */
    public List<Column> getColumns(List<String> list) {
        int k = -1;
        String columnName = null;
        String javaTypeName = null;
        List<Column> columns = new ArrayList<Column>();

        for(String name : list) {
            k = name.indexOf(":");

            if(k > -1) {
                columnName = name.substring(0, k);
                javaTypeName = name.substring(k + 1);
            }
            else {
                columnName = name;
                javaTypeName = "unknown";
            }

            Column column = new Column();
            column.setColumnCode(columnName);
            column.setColumnName(columnName);
            column.setJavaTypeName(javaTypeName);
            columns.add(column);
        }

        return columns;
    }

    /**
     * @param stream
     * @param list
     * @return List<String>
     */
    public List<String> parse(StringStream stream, List<String> list) {
        char c;
        int i = 0;
        StringBuilder buffer = new StringBuilder();
        String value = null;

        while((i = stream.read()) != EOF) {
            c = (char)i;

            if(c == '"') {
                while((i = stream.read()) != EOF) {
                    c = (char)i;

                    if(c == '\\') {
                        this.escape(stream, buffer);
                    }
                    else if(c == '"') {
                        i = stream.read();

                        if(i != EOF && i != ',') {
                            throw new RuntimeException("Bad format !");
                        }

                        break;
                    }
                    else {
                        buffer.append(c);
                    }
                }
                
                list.add(buffer.toString());
                buffer.setLength(0);
            }
            else {
                buffer.append(c);

                while((i = stream.read()) != EOF) {
                    c = (char)i;

                    if(c != ',') {
                        buffer.append(c);
                    }
                    else {
                        break;
                    }
                }

                value = buffer.toString().trim();
                
                if(value.equals("NULL")) {
                    list.add(null);
                }
                else {
                    list.add(value);
                }
                buffer.setLength(0);
            }
        }
        return list;
    }

    /**
     * @param value
     * @return Date
     */
    private Date getDate(String value) {
        try {
            long timeMillis = Long.parseLong(value, 10);
            return new Date(timeMillis);
        }
        catch(Exception e) {
        }
        return null;
    }

    /**
     * @param value
     * @return Date
     */
    private Timestamp getTimestamp(String value) {
        try {
            long timeMillis = Long.parseLong(value, 10);
            return new Timestamp(timeMillis);
        }
        catch(Exception e) {
        }
        return null;
    }

    /**
     * @param stream
     * @param buffer
     */
    private void escape(StringStream stream, StringBuilder buffer) {
        char c = (char)(stream.read());

        if(c != EOF) {
            switch(c) {
                case 'n': {
                    buffer.append('\n');
                    break;
                }
                case 't': {
                    buffer.append('\t');
                    break;
                }
                case 'b': {
                    buffer.append('\b');
                    break;
                }
                case 'r': {
                    buffer.append('\r');
                    break;
                }
                case 'f': {
                    buffer.append('\f');
                    break;
                }
                case '\'': {
                    buffer.append('\'');
                    break;
                }
                case '\"': {
                    buffer.append('\"');
                    break;
                }
                case '\\': {
                    buffer.append('\\');
                    break;
                }
                case 'u': {
                    char[] cbuf = new char[4];
                    int i = stream.read(cbuf);

                    if(i == 4) {
                        String hex = new String(cbuf);

                        try {
                            Integer value = Integer.parseInt(hex, 16);
                            buffer.append((char)(value.intValue()));
                        }
                        catch(NumberFormatException e) {
                        }
                    }

                    break;
                }
                default: {
                    stream.back();

                    char[] cbuf = new char[3];
                    int i = stream.read(cbuf);

                    if(i == 3) {
                        String oct = new String(cbuf);

                        try {
                            Integer value = Integer.parseInt(oct, 8);
                            buffer.append((char)(value.intValue()));
                        }
                        catch(NumberFormatException e) {
                        }
                    }

                    break;
                }
            }
        }
    }

    /**
     * @return the processHandler
     */
    public ProcessHandler getProcessHandler() {
        return this.processHandler;
    }

    /**
     * @param processHandler the processHandler to set
     */
    public void setProcessHandler(ProcessHandler processHandler) {
        this.processHandler = processHandler;
    }
}
